[Chapter 20] The checkcompat() Cookbook
Author: Bryan Costales & Eric Allman
Date: 1999-01-06T19:24:05Z

#

| Chapter 20 |

# 20. The checkcompat() Cookbook

Contents:
How checkcompat() Works
The Cookbook
Alphabetized V8.8 Subroutines

Inside sendmail is the often overlooked checkcompat() routine. It has existed since Version 3 and is intended to allow the site administrator to accept, reject, and log mail delivery attempts. Among its uses, and those we will illustrate, are the following:

- Refuse to act as a mail gateway. Reject any mail that is both from and to any outside address.

- Limit the size of guest messages. Screen messages based on user uid, and reject those for the class of users who are guests when the message is over a specified limit in bytes.

- Verify identd(8) information. Compare the host information in `$s` to that in `$_` and log a warning if they differ.

- Prune `Received:` headers at a firewall. Make all outgoing mail appear fresh as it exits the internal network via a firewall.

- Reject mail from spamming or mail-bombing sites. Look up the sender in an external database and bounce the message if the sender is listed as "bad."

The checkcompat() routine is located in the file src/conf.c. That file contains comments describing one way to code checkcompat(). In this chapter we show you other ways to code it.

The checkcompat() routine is inherently "internal," meaning that it must understand internal data structures that may change. [1] Since you are modifying source code, you have to be prepared to read source code. In this chapter we offer examples of ways to use checkcompat(). Be aware that they are examples only, and you will need C programming skill to extend them to real-world situations.

> [1] V8.8 sendmail also offers a `check_compat` rule set (see Section 29.10.4, "The check_compat Rule Set") that can perform some of the checkcompat() routine's functionality at the rule set level. This is one way to avoid having to program in the C language.

The checkcompat() routine is called for every delivery attempt to every recipient. When designing a checkcompat() routine of your own, be aware that a cascade of errors may propagate if you are not careful with your design. Logging a warning based on the sender, for example, may result in multiple warnings when there are multiple recipients.

## 20.1 How checkcompat() Works

When sendmail prepares to deliver mail, it first checks the size of the mail message and rejects (bounces) it if it is larger than the limit imposed by the `M=` delivery agent equate (see Section 30.4.7, M=). V8.8 sendmail then calls the `check_compat` rule set (see Section 29.10.4). Next, all versions of sendmail call the checkcompat() routine.

The checkcompat() routine lies in a unique position within the sendmail code. It is the one place where both the sender and recipient addresses are available at the same time. Since it precedes actual delivery, all the information needed for delivery is available to you for checking.

If checkcompat() returns EX_OK, as defined in <sysexits.h>, the mail message is considered okay and delivered. Otherwise, the message is bounced. If you wish the message to be requeued instead of bounced, you can return EX_TEMPFAIL.

Again note that the checkcompat() routine is called once for each recipient.

### 20.1.1 Arguments Passed to checkcompat()

The checkcompat() is found in the C language source file src/conf.c. Inside that file you will find it declared like this:

>     checkcompat(to, e)
>             register ADDRESS *to;
>             register ENVELOPE *e;

Here, `to` is a pointer to a structure of typedef ADDRESS that contains information about the recipient. And `e` is a pointer to a structure (actually a linked list of structures) of typedef ENVELOPE that contains information about the current envelope.

The members of the `ADDRESS *to` structure are shown in Table 20.1. Note that these members are correct for V8.8 sendmail only. Also note that the table shows only those members that may be useful in a checkcompat() routine (see sendmail.h for the other members of `*to`).

Table 20.1: ADDRESS *to Members
Type | Member | Description
--- | --- | ---
`char *` | `q_paddr` | The address in a form suitable for printing
`char *` | `q_user` | The user part (`$:`) from rule set 0 (see Section 29.6, "Rule Set 0")
`char *` | `q_ruser` | The login name for this user, if known
`char *` | `q_host` | The host part (`$@`) from rule set 0 (see Section 29.6)
`struct mailer *` | `q_mailer` | The delivery agent (`$#`) from rule set 0 (see Section 29.6)
`u_long` | `q_flags` | Status flags (see Section 37.3.1, "The Output Produced by printaddr()" in Section 37.3.1)
`uid_t` | `q_uid` | The uid of the `q_ruser`, if known
`gid_t` | `q_gid` | The gid of the `q_ruser`, if known
`char *` | `q_home` | The home directory (path), if delivery is local
`char *` | `q_fullname` | The (gecos) full name of `q_ruser`, if known
`struct address *` | `q_next` | Link to the next ADDRESS in the chain
`struct address *` | `q_alias` | The alias that yielded this address
`char *` | `q_owner` | The owner of `q_alias`

The members of the `ENVELOPE *e` structure are shown in Table 20.2. Note that these members are correct for V8.8 sendmail only. Also note that the table shows only those members that may be useful in a checkcompat() routine (see sendmail.h for other members of `*e`).

Table 20.2: ENVELOPE *e Members
Type | Member | Description
--- | --- | ---
`HDR *` | `e_header` | Linked list of headers
`time_t` | `e_ctime` | Time message first placed into the queue
`ADDRESS` | `e_from` | The sender
`ADDRESS *` | `e_sendqueue` | Linked list of recipients
`long` | `e_msgsize` | Size of the message in bytes
`long` | `e_flags` | Envelope flags (see Table 37.3 in Section 37.5.12, -d2.1)
`int` | `e_nrcpts` | Number of recipients
`short` | `e_hopcount` | The hop count for the message

The checkcompat() routine is a powerful internal hook inside sendmail. It is so internal and powerful, in fact, that if you are truly clever, you can even use checkcompat() to modify rewrite rules at runtime (scary but possible).

### 20.1.2 Global Variables

Over 100 global variables are used by V8.8 sendmail. They are all listed in sendmail.h and conf.c with "lite" comments. Global variables store information such as sendmail's option values, file descriptor values, macro values, class lists, and database access information. Any can be modified inside checkcompat(), but before attempting to do so, study the sendmail C source code to anticipate any unexpected side effects.

In general, you can use almost any of the global variables when designing your own checkcompat() routine. The four most interesting are the following:

`RealHostAddr`

  The IP address of the sending host. This is a union of several sockaddr_ types depending on your selection of protocol type. This can be zero for locally submitted mail.

`RealHostName`

  A string containing the definitive canonical name of the sending host. If it can't be resolved to a name, it will contain the host's IP number in text form, surrounded by square brackets.

`LogLevel`

  This variable determines the amount of logging that sendmail does. It is initially set with the `LogLevel` (`L`) option (see Section 34.8.33, LogLevel (L)). You might want to use checkcompat() to detect questionable connections and, if any are detected, to increase the value in `LogLevel` to 12. This will cause both sides of every subsequent SMTP connection to be logged.

`MatchGecos`

  Whether or not unmatched local looking names are looked up in the passwd(5) file is under the control of the `MatchGECOS` (`G`) option (see Section 34.8.34, MatchGECOS (G)). Because this kind of lookup is expensive, you might wish to enable it only during nonbusiness hours. One way to do this would be by modifying the `MatchGecos` variable inside checkcompat().

19.6 Alphabetized m4 Macros |  | 20.2 The Cookbook